package zjl;

import zjl.base.BizRequest;
import zjl.base.DirectedLink;
import zjl.graph.BaseEdge;
import zjl.graph.Path;

import java.util.*;

public class Individual implements Chromosome<Individual>, Comparable<Individual>, Cloneable {

    private Random random = new Random(System.currentTimeMillis());

    // 资源拓扑信息和实时确定性业务需求
    private ResourceStore resourceStore;

    // 基因组信息
    private int[] gene = null;

    // 基因组适应度评价值
    private double fitness = 0.0;

    public Individual(ResourceStore rs) {
        this.resourceStore = rs;
        this.gene = new int[rs.getBizRequestSize()];
        this.init();
    }

    private void init() {
        int reqSize = this.resourceStore.getBizRequestSize();
        for (int i = 0; i < reqSize; i++) {
            int kspSize = this.resourceStore.getBizRequestAt(i).getKspSize();
            int pidx = this.random.nextInt(kspSize);
            this.gene[i] = pidx;
        }
    }

    public int getGeneAt(int idx) {
        return this.gene[idx];
    }

    public double getFitness() {
        return fitness;
    }

    @Override
    public List<Individual> crossover(Individual that) {
        // 我们使用单点交叉法作为个体基因交叉的方法
        Individual thisClone = this.clone();
        Individual thatClone = this.clone();

        // 随机产生交叉互换位置并执行互换操作
        int pos = this.random.nextInt(this.gene.length);
        for (int i = 0; i < pos; i++) {
            int t = thisClone.gene[i];
            thisClone.gene[i] = thatClone.gene[i];
            thatClone.gene[i] = t;
        }

        return Arrays.asList(thisClone, thatClone);
    }

    public Individual mutate() {
        Individual c = this.clone();

        // 确定变异基因位置
        int idx = this.random.nextInt(this.gene.length);
        int range = this.resourceStore.getBizRequestAt(idx).getKspSize();

        // 执行基因变异操作
        int curr = c.gene[idx];
        int next = this.random.nextInt(range);
        while (curr == next) {
            next = this.random.nextInt(range);
        }
        c.gene[idx] = next;

        return c;
    }

    @Override
    public boolean estimate() {
        // 评价方法：将每条链路上的带宽利用率作为评价指标
        // 优化目标：最小化最大带宽利用率

        double maxUsage = 0.0;
        int clusterPeriod = this.resourceStore.getClusterPeriod();
        List<BaseEdge> edgeList = this.resourceStore.getEdgeListOfOriginGraph();
        Map<BaseEdge, Integer> linkDataMap = new HashMap<>();

        // 初始化，每条链路的使用数据量为0
        for (BaseEdge be : edgeList) {
            linkDataMap.put(be, 0);
        }

        // 根据每个实时数据业务的发送周期及数据量确定在集群周期中的总数据量
        for (int ri = 0; ri < this.resourceStore.getBizRequestSize(); ri++) {
            BizRequest request = this.resourceStore.getBizRequestAt(ri);
            Path selectedPath = request.getKspPath(this.gene[ri]);
            for (BaseEdge be : selectedPath.getEdgeList()) {
                int tlen = request.getLength() * clusterPeriod / request.getPeriod();
                linkDataMap.put(be, linkDataMap.get(be) + tlen);
            }
        }

        // 根据链路在集群周期内的总承受数据量，确定带宽使用率
        for (BaseEdge be : edgeList) {
            int allen = ((DirectedLink)be).getBandwidth() * clusterPeriod;
            double usage = ((double)(linkDataMap.get(be)))/allen;
            if (usage > maxUsage)
                maxUsage = usage;
        }

        // 将最大链路带宽利用率的反值作为适应度评价值
        if (maxUsage <= 1.0) {
            this.fitness = 1.0 - maxUsage;
            return true;
        }

        // 不可行，则将适应度设置为-1.0
        this.fitness = -1.0;
        return false;
    }

    @Override
    public Individual clone() {
        Individual rc = new Individual(this.resourceStore);
        System.arraycopy(this.gene, 0, rc.gene, 0, this.gene.length);

        return rc;
    }

    @Override
    public int compareTo(Individual o) {
        if (this.fitness > o.fitness)
            return -1;
        else if (this.fitness < o.fitness)
            return 1;

        return 0;
    }

    @Override
    public String toString() {
        String str = "INDIVIDUAL INFORMATION = {";
        str += "\n  gene = " + Arrays.toString(gene);
        str += "\n  fitness = " + fitness;
        str += "\n}";
        return str;
    }
}
